package tirateima.controlador;


import java.util.Stack;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import tirateima.gui.variaveis.VarGrade;
import tirateima.gui.variaveis.VarRecord;
import tirateima.gui.variaveis.Variavel;

/**
 * Modela um comando de entrada do usuário.
 * 
 * @author Vinícius
 *
 */
public class CommandReadInput extends Command{

	private Stack<Object> var_stack;
	private int num_vars; //numero de variaveis que serao lidas
	private Stack<Object> pilhaAux;
	private Object value;
	private Controlador c;
	
	public boolean finalizou_leitura = false;

	private volatile Thread th;

	public CommandReadInput(Stack<Object> var_stack, int num_vars) {
		this.num_vars = num_vars;
		this.var_stack = var_stack;
	}

	public CommandReadInput getInstance() {
		return this;
	}


	/**
	 * Executa o comando de leitura do usuário, setando um valor recebido na 
	 * variável.
	 * @throws ExecutionException 
	 */
	@Override
	public void execute(final Controlador c) throws TiraTeimaLanguageException, ExecutionException {
		//Copia o controlador
		this.c = c;
		//Copia pilha para possível restauração.
		pilhaAux = new Stack<Object>();
		pilhaAux = copia(var_stack);
		//Recebe entrada do usuário.
		/*		value = JOptionPane.showInputDialog(null, 
				"Entre com o valor de " + var_stack.get(0) + ":", 
				"Entrada de Dados", 
				JOptionPane.DEFAULT_OPTION, 
				null, 
				null, 
				null);*/
		
		finalizou_leitura = false;
		
		
		Runnable runn1 = new Runnable() {
			public void run() {
				getInstance().c.proxEstadoEnabled(false);
			}
		};
		SwingUtilities.invokeLater(runn1);
		
		/*
		 * Esta entrada tem que ser feita numa nova thread, caso contrário o Swing não acaba de renderizar a JTextArea do console
		 */
		Runnable runn2 = new Runnable() {
			public void run() {
				Asker asker = new Asker(getInstance(),getInstance().c, 255, getInstance().c.console); // Asker defined immediately below.
				String response = asker.response();
				value = response;

				//Informa condição de prosseguimento
				if(value == null){
					try {
						throw new ExecutionException("Valor não informado.", this.getClass().toString());
					} catch (ExecutionException e) {
						//e.printStackTrace();
					}
				}
				
				//Tenta setar o valor de entrada para o usuário.
				try{
					//Se for null é porque o usuário voltou sem digitar nada então não faz nada
					if (value != null) {
						String[] values = ((String)value).trim().split(" ");
						int linhas_usadas = 1;
						Stack<Object> s = var_stack;
						while (values.length != num_vars) {
							asker = new Asker(getInstance(),getInstance().c, 255, getInstance().c.console);
							response = asker.response();
							value = response;
							String[] values2 = ((String)value).trim().split(" ");
							values = concatArrays(values, values2);
							linhas_usadas++;
							
						}
						finalizou_leitura = true;
						int i = 0;
						for (String str : values) {
							Stack<Object> pilha = new Stack<Object>();
							pilha.add(var_stack.get(i));
							//If para o caso de uma leitura de array, exemplo: read(v[2]);
							if (var_stack.size() > (i+1)) {
								if (var_stack.get(i+1) instanceof Index) {
									//if para aceitar casos onde a entrada é por exemplo: pessoas[i].nome
									Variavel v = c.mostrador.getCopiaVariavel((String)var_stack.get(i));
									if (v instanceof VarGrade) {
										if (((VarGrade) v).getVariaveis().get(0) instanceof VarRecord) {
											if (var_stack.size() > (i+3)) {
												pilha.add(var_stack.get(i+1));
												i++;
												pilha.add(var_stack.get(i+1));
												i++;
												pilha.add(var_stack.get(i+1));
												i++;
											}
											else if (var_stack.size() > (i+2)) {
												pilha.add(var_stack.get(i+1));
												i++;
												pilha.add(var_stack.get(i+1));
												i++;
											}
										}
										else {
											//else para aceitar casos onde a entrada é por exemplo: read(v[2]);
											pilha.add(var_stack.get(i+1));
											i++;
										}
									}
								}
							}
							setValue(getInstance().c, pilha, str);
							i++;
						}
						c.saveEstadoConsole(linhas_usadas);
					}
				}
				catch(Exception e){
					var_stack = getInstance().pilhaAux;
					try {
						for (int i = 0; i < var_stack.size(); i++) {
							Stack<Object> pilha = new Stack<Object>();
							pilha.add(var_stack.get(i));
							setValue(getInstance().c, pilha, null);
						}
					} catch (TiraTeimaLanguageException e2) {
						e2.printStackTrace();
					}
					JOptionPane.showMessageDialog(null, 
							"Valor Incorreto!", 
							"Erro", 
							JOptionPane.ERROR_MESSAGE);
					
					
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
								try {
									SwingUtilities.invokeLater(new Runnable() {
										public void run() {
											c.console.setEstado(c.getEstadoConsoleAnterior());
											c.mostrador.requestFocus();
											c.console.requestFocus();
										}
									});
									getInstance().execute(c);
								} catch (TiraTeimaLanguageException e) {
									e.printStackTrace();
								} catch (ExecutionException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
						}
					});
					
					//TODO: lançar mensagens diferentes para os diferentes tipos de exceção (mais didático).
					/*try {
						throw new ExecutionException("Valor incorreto.", this.getClass().toString());
					} catch (ExecutionException e1) {
						e1.printStackTrace();
					}*/
				}

			}
		};
		
		
		th = new Thread(runn2);
		th.start();
		
	}

	@Override
	public void revert(final Controlador c) throws TiraTeimaLanguageException,
	ExecutionException {
		th.interrupt();
		
		int i = -1;
		for (int j = 0; j < num_vars; j++) {
			i++;
			Stack<Object> pilha = new Stack<Object>();
			pilha.add(var_stack.get(i));
			if (var_stack.size() > (i+1)) {
				if (var_stack.get(i+1) instanceof Index) {
					//if para aceitar casos onde a entrada é por exemplo: pessoas[i].nome
					Variavel v = c.mostrador.getCopiaVariavel((String)var_stack.get(i));
					if (v instanceof VarGrade) {
						if (((VarGrade) v).getVariaveis().get(0) instanceof VarRecord) {
							if (var_stack.size() > (i+2)) {
								pilha.add(var_stack.get(i+1));
								i++;
								pilha.add(var_stack.get(i+1));
								i++;
							}
						}
						else {
							//else para aceitar casos onde a entrada é por exemplo: read(v[2]);
							pilha.add(var_stack.get(i+1));
							i++;
						}
					}
				}
			}
			setValue(getInstance().c, pilha, null);
		}
		
	}

	/**
	 * Copia pilha para possível restauração.
	 * 
	 * @param var_stack
	 * @return
	 */
	private Stack<Object> copia(Stack<Object> pilha) {
		Stack<Object> pilhaCopia = new Stack<Object>();
		Stack<Object> pilhaAux = new Stack<Object>();
		Object elemento;
		//Passa elementos para uma pilha intermediária
		while(!pilha.empty()){
			elemento = pilha.pop();
			pilhaAux.push(elemento);
		}
		//Copia pilha e restaura pilha original
		while(!pilhaAux.empty()){
			elemento = pilhaAux.pop();
			pilhaCopia.push(elemento);
			pilha.push(elemento);
		}
		return pilhaCopia;
	}
	
	

	private String[] concatArrays(String[] A, String[] B) {
		String[] C= new String[A.length+B.length];
		System.arraycopy(A, 0, C, 0, A.length);
		System.arraycopy(B, 0, C, A.length, B.length);

		return C;
	}

}


